#!/usr/bin/env python
# coding: utf-8

# ====================== 
# 
# ChenNingjun
# Shen Xiaoyu
# 
# ======================

import sys
import copy
import time
import os
import re
import collections

from .basecase import BaseCase
from .tnxxx_base import *
from .pc_base import *

# ==============================================================================
"""
<cfg_info_list 配置信息说明> 090604 Chen Ningjun

cfg_info_list = [cfg_info(list), ...]
cfg_info = [cmd_list(list), param_dic(directory), ne_id(int or str), port(int), comment(str可缺省), cfg_interval(float可缺省)]
# 配置信息主要包含 (原始命令信息列表，参数字典，发送到哪个网元，哪个端口，注释, 延迟时间)

param_dic = { KEY(str) : VALUE(str or list), ... }
# 不需要替换的话，给个空字典就行了
KEY : 需要替换的目标字符串
VALUE : 替换目标(字符串 或者 字符串列表)

cmd_list = [cmd_info(list or str), ...]
cmd_info = cmd(str) 或者 [cmd(str), rtn_success_flag(None/str/function/[function, param_list] 可缺省), cmd_interval(float可缺省) ]
cmd_info_full = [cmd(str), rtn_success_flag(None/str/function/[function, param_list])/dict, cmd_interval(float)]

# 原始命令信息: 可以直接是 命令字符串，也可以是个列表：[命令字符串, 返回成功标记 或 返回串处理函数, 命令之间的停顿时间]
cmd : 原始命令字符串
rtn_success_flag : 返回成功标记。命令执行后会返回rtn。
# 不指定rtn_success_flag                  下发命令，返回提示符标识成功（默认rtn_success_flag == ""）
# rtn_success_flag == ""                  下发命令，返回提示符标识成功
# rtn_success_flag == None                下发命令，不需要返回 (调用sendonly接口)
# type(rtn_success_flag) == str           下发命令，返回串包含str子串标识成功
# type(rtn_success_flag) == function      下发命令，返回串传递给 rtn_success_flag 函数处理，函数返回True标识成功
# type(rtn_success_flag) == [function, param]  同上，区别是支持给 rtn_success_flag 函数传递额外的参数 （参数类型的正确性须由用户自行保证）
#                                       注意：function必须是全局函数

cmd_interval : 命令之间的停顿时间(默认为0秒, 不停顿)
fun_param : rtn_success_flag是函数的时候，该参数表示传递给函数的参数；否则该参数无效。

ne_id : 发送到哪个网元. XML拓扑配置文件中所配置的网元id。
port : 发送到哪个端口.参见文件头上的定义。

comment : 这组命令的注释。执行这组命令前输出。

cfg_interval: 执行这组命令后，停止多少时间(默认为0.1秒)
"""

# ==============================================================================
"""
<redirection 功能说明> 090604 Chen Ningjun

命令重定向到文件(redirection 功能): 可以利用python脚本生成纯命令的配置脚本！！！

启用方式1：在case中调用init_redirection()。
    例：case = TestCase()
        case.init_redirection() # 重定向到文件
        case.execute()
    注:redirection操作是在cmd_list层面处理的，所以不能在回调函数里面(命令列表发了一半的时候)调用。。。

启用方式2：在 case 的 OPTIONS 中添加 outputfile 参数。
    例：OPTIONS = [
        {
           'opt' : '--cfgfile',
           'dest': 'cfgfile',
           'metavar' : 'PATH',
           'help' : 'the file of netring configuration',
           'required' : False,
           'default' : "tn703.xml"
        },
        {
           'opt' : '--outputfile',
           'dest': 'outputfile',
           'metavar' : 'PATH',
           'help' : 'the file of netring redirection',
           'required' : False,
           'default' : "d:\Tn7x5_cmd_redirection.txt"
        },
        ]
注：不指定文件名或者指定的文件名无效的情况，都将默认为 d:\Tn7x5_cmd_redirection.txt
"""

# ==============================================================================
# 预定义命令列表
"""
预定义命令列表  说明： 090902 Chen Ningjun

1.恢复出厂配置

2.设置逻辑板 
  case可通过 cmd_list_set_lgcard = dic_cmd_list_set_lgcard[ne_id] 获取指定网元设置逻辑板的命令列表
  注意：dic_cmd_list_set_lgcard 在 _cnostruct_dic_cmd_list_set_lgcard 里面进行设置。
        由于 _cnostruct_dic_cmd_list_set_lgcard 在 __init__ 中才执行，因此对dic_cmd_list_set_lgcard的访问不能放在顶层做
"""

# 恢复出厂配置
cmd_list_factory_mode = [
    "configure terminal",
    "restore factory-mode full",
    "exit",
]

# 设置逻辑板
dic_cmd_list_set_lgcard = {}





# ==============================================================================

def abstract_base_class(self, Type):
    if self.__class__ is Type :
        strErr = "virtual base class %s should not construct instance directly"%(Type.__name__)
        raise NotImplementedError(strErr)

def abstract_method():
    caller = inspect.getouterframes(inspect.currentframe())[1][3]
    raise NotImplementedError(caller + ' must be implemented in subclass')

#打印当前位置调用栈
import inspect
def dump_call_stack(deepness = 3) :
    call_stack = inspect.getouterframes(inspect.currentframe())
    #[(frame, __FILE__, __LINE__, function_name, code, 0),...]
    call_stack_deepness = len(call_stack) - 1
    if call_stack_deepness < 1 :
        return ''
    dump_deepness = int(deepness)
    dump_deepness = dump_deepness if (dump_deepness <= call_stack_deepness) else call_stack_deepness
    dump_str = '\n'
    for idx in range(dump_deepness, 0, -1) :
        file = call_stack[idx][1]
        line = call_stack[idx][2]
        func = call_stack[idx][3]
        code = call_stack[idx][4]
        dump_str += 'File ' + file + ', in ' + func + ', line ' + str(line) + '\n'
        dump_str += str(code)
    return dump_str

# 异常调用栈回滚
class string_buf_file : # buf class for re-direction
    def __init__(self, title_tag="[Traceback]\n"):
        self.m_buf = str(title_tag)
    def write(self, _str):
        self.m_buf += _str
    def read(self):
        return self.m_buf
import sys, traceback
def dump_exc_call_stack(deepness = 5) :
    dump_deepness = int(deepness)
    dump_deepness = dump_deepness if (dump_deepness>0 and dump_deepness<255) else 5
    file = string_buf_file(None)
    etype, value, tb = sys.exc_info()
    traceback.print_exception(etype, value, tb, dump_deepness, file)
    return file.read()

class TnxxxCfg_BaseCase(BaseCase) :
    """
    NeSvcCfg_BaseCase
    """
    OPTIONS = [
    ]
    
    # 命令重定向到文件(redirection 功能)！
    switch_redirection = 0             # 开关: 把命令序列输出到文件而不是实际网元
    filename_redirection = ""          # 命令序列文件的路径
    fileobj_redirection = None         # 文件对象
    # 根据outputfile属性，初始化 redirection 功能
    def ___init_redirection_from_option(self) :
        if hasattr(self, "options") and hasattr(self.options, "outputfile") :
            self.switch_redirection = 1
            if issubclass(self.options.outputfile.__class__, str) and len(self.options.outputfile)>0 : # 可考虑加强对路径的判断。。。
                self.filename_redirection = self.options.outputfile
            else : self.filename_redirection = "d:\Tn7x5_cmd_redirection.txt"
        return True
    # 强制启用 redirection 功能(脱离对outputfile属性的依赖，用于代码显式调用)
    # filename="" 指定输出文件的路径
    def init_redirection(self, filename="") :    
        self.switch_redirection = 1
        if issubclass(filename.__class__, str) and len(filename)>0 :
            self.filename_redirection = filename
        else : self.filename_redirection = "d:\Tn7x5_cmd_redirection.txt"
        return True
    # 命令重定向
    def __do_redirection(self, cmd) :
        if 0 != self.switch_redirection :
            if None == self.fileobj_redirection :
                self.fileobj_redirection = open(self.filename_redirection, 'w')    # 打开文件(并清空)
            self.fileobj_redirection.write(cmd + "\n")
            self.logger.info(cmd)
            return True
        return False
    def __close_redirection(self) :
        if None != self.fileobj_redirection :
            self.fileobj_redirection.close()
            self.fileobj_redirection = None
        return True
    
    
    def __init__(self) :
        """
        """
        abstract_base_class(self, TnxxxCfg_BaseCase)
        BaseCase.__init__(self)
        self.tnxxx = tnxxx_base()
        self.pc_shell = pc_base()
        self.nes_info = {}
        self.run_init()
        
    def __del__(self) :
        self.tnxxx.disconnect()

    # 子类重写的接口函数 ****************** #
    def get_cfg_info_list(self) :
        """
        获取 配置信息 列表
        """
        # abstract_method() # 现在允许子类不实现此函数
        return []

    # ********************************* #
    def __is_rtn_success_flag_valid(self, rtn_success_flag) :
        if (None == rtn_success_flag)                       \
        or issubclass(rtn_success_flag.__class__, str)      \
        or isinstance(rtn_success_flag, collections.Callable) \
        or ( issubclass(rtn_success_flag.__class__, list) and len(rtn_success_flag) >= 2 and isinstance(rtn_success_flag[0], collections.Callable)):
            return True
        self.logger.error( "_send_cmd_list: rtn_success_flag obj is bad  " )
        return False

    def __is_cmd_info_full_valid(self, cmd_info_full) :
        if ( not issubclass(cmd_info_full.__class__, list) )            \
        or ( not (len(cmd_info_full)>=3) )                              \
        or ( not issubclass(cmd_info_full[0].__class__, str) )          \
        or ( not self.__is_rtn_success_flag_valid(cmd_info_full[1]) )   \
        or ( not issubclass(cmd_info_full[2].__class__, float) ):
            self.logger.error( "_send_cmd_list: cmd_info_full obj is bad  " )
            return False
        return True

    # 获取param_dic的key列表，并且从长到短排序 => 等效于“贪婪法则”替换
    def get_sorted_keys(self, param_dic) :
        # key_list = param_dic.keys()
        key_list = list(param_dic.keys())
        sorted_key_list = []
        for key in key_list :
            insert_pos = -1
            for idx in range(len(sorted_key_list)) :
                if len(key) > len(sorted_key_list[idx]) :
                    insert_pos = idx
                    break
            if insert_pos == -1:   # find failed
                sorted_key_list.append(key)
            else :
                sorted_key_list.insert(insert_pos, key)
        return sorted_key_list

    # 参数替换 -> 得到命令列表
    def ___do_param_replace(self, cmd, rtn_success_flag, cmd_interval, param_dic) :
        cmd_list_full_temp = [[cmd, rtn_success_flag, cmd_interval], ]
        # 不替换直接返回
        if len(param_dic) <= 0 :
            return cmd_list_full_temp 
        sorted_key_list = self.get_sorted_keys(param_dic)
        # 遍历替换字典
        for key in sorted_key_list :
            if not issubclass(key.__class__, str) :
                self.logger.error( "detect invalid param_dic pair! %s : %s" %( str(key), str(param_dic[key]) ) )
                continue
            # 注意：前面的替换可能已经把一条原始命令扩展成N条新命令了
            # 替换字典：对目标项进行直接的值替换
            if not issubclass(param_dic[key].__class__, list) :
                for cmd_info_full_item in cmd_list_full_temp :
                    if not self.____is_need_replace(cmd_info_full_item, key):
                        continue
                    cmd_info_full_item = self.____replace_it(cmd_info_full_item, key, str(param_dic[key]))
            # 替换字典：对目标项进行列表替换 -> 删除旧项，新生成N项(每项替换一个列表成员)
            else :
                if len(param_dic[key]) <=0 :
                    self.logger.error( "detect invalid param_dic pair! %s : %s "%( str(key), str(param_dic[key]) ) )
                    continue
                cmd_info_full_index = 0
                while cmd_info_full_index < len(cmd_list_full_temp) :
                    cmd_info_full_item = cmd_list_full_temp[cmd_info_full_index]
                    if not self.____is_need_replace(cmd_info_full_item, key):
                        cmd_info_full_index += 1
                        continue
                    cmd_info_full_item = cmd_list_full_temp.pop(cmd_info_full_index)
                    for item in param_dic[key] :
                        cmd_info_full_item_new = copy.deepcopy(cmd_info_full_item)
                        cmd_info_full_item_new = self.____replace_it(cmd_info_full_item_new, key, str(item))
                        cmd_list_full_temp.insert(cmd_info_full_index, cmd_info_full_item_new)
                        cmd_info_full_index += 1
        return cmd_list_full_temp
    
    #判断any中是否存在key的string    
    def ____is_need_replace(self, any, key):
        if str == type(any) and any.find(key)>=0:
            return True
        if list==type(any):
            for v in any:
                if self.____is_need_replace(v, key):
                    return True
        if dict==type(any):
            for k,v in enumerate(any):
                if self.____is_need_replace(k, key) or \
                   self.____is_need_replace(v, key):
                    return True
        return False
    
    #将any中key的string替换成value    
    def ____replace_it(self, any, key, value):
        if str == type(any) and any.find(key)>=0:
            any = any.replace(key, value)
        if list==type(any):
            for i,v in enumerate(any):
                any[i] = self.____replace_it(v, key, value)
        if dict==type(any):
            for k,v in enumerate(any):
                k = self.____replace_it(k, key, value)
                v = self.____replace_it(v, key, value)
                any_new[k] = v
            any = any_new
        return any
       
    def ___do_cmd_split(self, cmd_list_full_temp) :
        cmd_info_full_list = []                
        for cmd_info_full in cmd_list_full_temp :
            if cmd_info_full[0].find('\n') >= 0 :
                cmd_list_temp = cmd_info_full[0].splitlines()
                for index, cmd_temp in enumerate(cmd_list_temp) :
                    if index == len(cmd_list_temp) - 1 :
                        cmd_info_full_list.append( [str(cmd_temp), str(cmd_info_full[1]), cmd_info_full[2]] )
                    else :
                        cmd_info_full_list.append( [str(cmd_temp), str(""), cmd_info_full[2]] )
            else :
                cmd_info_full_list.append(cmd_info_full)
        return cmd_info_full_list

    def __parse_cmd_info(self, cmd_info, param_dic):
        # cmd_info 合法性检查! 最终获取一条原始命令信息
        cmd = ""
        rtn_success_flag = ""
        cmd_interval = float(0)
        # 原始命令信息 = 字符串
        if issubclass(cmd_info.__class__, str) :
            cmd = cmd_info
        # 原始命令信息 = 列表
        elif issubclass(cmd_info.__class__, list) :
            if len(cmd_info)>=1 and issubclass(cmd_info[0].__class__, str) :
                cmd = copy.deepcopy(cmd_info[0])
            if len(cmd_info)>=2 :
                rtn_success_flag = copy.deepcopy(cmd_info[1])
            if len(cmd_info)>=3 :
                cmd_interval = float(cmd_info[2])
            if len(cmd_info)>=4 :
                return None
        else :
            return None
        cmd_list_full_temp = self.___do_param_replace(cmd, rtn_success_flag, cmd_interval, param_dic)
        return self.___do_cmd_split(cmd_list_full_temp)        
        
    # 发送一条网元命令
    def __send_cmd(self, xxx_element, cmd_info_full, param_dic) :
        cmd = cmd_info_full[0]
        rtn_success_flag = cmd_info_full[1]
        cmd_interval = float(cmd_info_full[2])
        if not self.__is_rtn_success_flag_valid(rtn_success_flag) :
            self.logger.error( "__send_ne_cmd: rtn_success_flag '%s' is not valid "%( str(rtn_success_flag) ) )
            return False
        # 把命令序列输出到文件而不是实际网元
        if self.__do_redirection(cmd) :
            return True
        # rtn_success_flag==None  -> 使用 send_only 模式发送
        if None == rtn_success_flag :
            return xxx_element.send_only(cmd)
        # rtn_success_flag 是 str -> 使用 send 模式发送
        if issubclass(rtn_success_flag.__class__, str) :
            return xxx_element.send(cmd, rtn_success_flag)            
        # rtn_success_flag 是 function  -> 使用 send_rtn_str 模式发送, 并把返回串交给回调函数处理
        # if callable(rtn_success_flag) :
        if isinstance(rtn_success_flag, collections.Callable):
            rtn = xxx_element.send_rtn_str(cmd)
            if (len(rtn)>0) and (True==rtn_success_flag(rtn)) :
                return True
        # rtn_success_flag 是 [function, param_list], 处理同上，但会传递额外的参数。
        if issubclass(rtn_success_flag.__class__, list) \
        and  len(rtn_success_flag) >= 2                 \
        and isinstance(rtn_success_flag, collections.Callable):
            rtn = xxx_element.send_rtn_str(cmd)
            if (len(rtn)>0) and (True==rtn_success_flag[0](rtn, rtn_success_flag[1])) :
                return True
        # Error
        return False

    # 发送列表中的命令
    def _send_cmd_list(self, ne_id, slot_port, cmd_list, param_dic) :
        if len(cmd_list) <=0 :
            self.logger.error( "_send_cmd_list: cmd_list is NULL " )
            return False
        xxx_element = None
        # 非redirection情况下，才需要执行实际连接操作！
        if not self.__do_redirection("") :
            if type(slot_port)==int:
                slot = 0
                port = slot_port
            elif type(slot_port)==list:
                slot = slot_port[0]
                port = slot_port[1]
            else:
                self.logger.error( "_send_cmd_list: slot_port type error" )
                return False
            if port==PC_SHELL:
                xxx_element = self.pc_shell
                ip = '127.0.0.1'
            else:
                xxx_element = self.tnxxx
                ip = self.get_ne_ip(ne_id)
            if ip==None:
                return False
            if not xxx_element.connect(ip, slot, port):
                return False
        # redirection情况下，需要输出说明
        else :
            self.__do_redirection("*****cmd list: [ne id = %d, port = %s]*****"%(ne_id, str(slot_port)))
        for cmd_info in cmd_list:
            cmd_info_full_list = self.__parse_cmd_info(cmd_info, param_dic)
            if None == cmd_info_full_list:
                return False
            for cmd_info_full in cmd_info_full_list :
                if( not self.__is_cmd_info_full_valid(cmd_info_full) ) :
                    continue
                success = True
                cmd = cmd_info_full[0]
                rtn_success_flag = cmd_info_full[1]
                cmd_interval = float(cmd_info_full[2])
                success = self.__send_cmd(xxx_element, cmd_info_full, param_dic)   # 网元命令
                if not success :
                    self.logger.error( "__send_ne_cmd: '%s' execute failed "%(cmd) )
                    return False
                if cmd_interval > 0 :
                    time.sleep(cmd_interval)
        return True

    def __get_info_from_xml(self) :
        if hasattr(self, "options") and hasattr(self.options, "cfgfile") :
            ne_info_parse = tnxxx_ne_info_parse(self.options.cfgfile)
            self.nes_info = ne_info_parse.get_nes_info()
            return True
        return False
    
    def get_ne_ip(self, ne_id):
        # if self.nes_info.has_key(str(ne_id)) and self.nes_info[str(ne_id)].has_key("ip") :
        if str(ne_id) in self.nes_info and "ip" in self.nes_info[str(ne_id)] :
            return str(self.nes_info[str(ne_id)]["ip"])
        return None
    
    def set_ne_ip(self, ne_id, ip):
        # if not self.nes_info.has_key(str(ne_id)):
        if str(ne_id) not in self.nes_info:
            self.nes_info[str(ne_id)] = {}
        self.nes_info[str(ne_id)]["id"] = str(ne_id)
        self.nes_info[str(ne_id)]["ip"] = str(ip)
        return True
    
    def _cnostruct_dic_cmd_list_set_lgcard(self) :
        """
        ["1 CGF12", "2, CGT12", "3 OPCA"]
        """
        for ne_info in list(self.nes_info.values()) :
            cmd_list_set_lgcard = [
                "configure terminal",
                "chasis",
            ]
            for chss_info in list(ne_info["chss"].values()) :
                set_cmd = "set lgcard %s %s"%(chss_info["slot"], chss_info["type"])
                cmd_list_set_lgcard.append( str(set_cmd).lower() )
            cmd_list_set_lgcard.append("exit")
            cmd_list_set_lgcard.append("exit")
            dic_cmd_list_set_lgcard[int(ne_info["id"])] = cmd_list_set_lgcard

    __run_init_done = False
    def run_init(self, bForce = False) :
        if not bForce and self.__run_init_done:
            return True
        try :
            self.__run_init_done = False
            os.environ['ONSHS_PRINT_NE_RESULT'] ='1'
            if not self.__get_info_from_xml() :  # tn7x5NeInfo                
                return self.__run_init_done
            self._cnostruct_dic_cmd_list_set_lgcard()
            if len(dic_cmd_list_set_lgcard) <= 0 :
                self.logger.error( "cnostruct_dic_lgcard_list failed! " )
                return self.__run_init_done
            self.__run_init_done = True
        except :
            self.logger.error( dump_exc_call_stack() )
            self.__run_init_done = False
        return self.__run_init_done
    
    # 执行一个cfg_info
    def srun(self, cfg_info, index=0) :
        if not self.run_init() :
            return False
        # cfg_info check
        if ( not issubclass(cfg_info.__class__, list) )     \
        or ( not len(cfg_info) >= 4 )                       \
        or ( not issubclass(cfg_info[0].__class__, list) )  \
        or ( not issubclass(cfg_info[1].__class__, dict) ) :
            self.logger.error( "cfg_info(index=%d) obj is bad "%(index) )
            return False
        # get param
        comment = ""
        cfg_interval = 0.1
        cmd_list = cfg_info[0]
        param_dic = cfg_info[1]
        ne_id = int(cfg_info[2])
        port = cfg_info[3]
        if len(cfg_info) >= 5 and len(str(cfg_info[4])) > 0 :
            comment = str(cfg_info[4])
        if len(cfg_info) >= 6 and float(cfg_info[5]) >= 0.1 :
            cfg_interval = float(cfg_info[5])
        if len(comment) > 0 :
            self.logger.info(comment)             # 打印注释
        success = self._send_cmd_list(ne_id, port, cmd_list, param_dic)
        time.sleep(cfg_interval)
        return success
    
    # 执行放在列表里面的多个 cfg_info
    def run(self, cfg_info_list) :
        # cfg_info_list check
        if not issubclass(cfg_info_list.__class__, list) :
            self.logger.error( "cfg_info_list bad type! " )
            return False
        if len(cfg_info_list) <= 0 :
            self.logger.error( "cfg_info_count <= 0 " )
            return False
        result = True
        for index, cfg_info in enumerate(cfg_info_list) :
            result = result and self.srun(cfg_info, index)
            if not result : break                   # 一个列表执行失败，结束下面的指令的执行
        return result
    
    # 子类重写的接口函数 ****************** #
    def _execute(self):
        cfg_info_list = self.get_cfg_info_list()
        return self.run(cfg_info_list)
    
    def execute(self, bInJob=True):
        result = True
        self.logger.info( ">>>- %s Begin... -------- "%(self.__class__.__name__ ) )
        try :
            self.___init_redirection_from_option()
            result = self._execute()
            self.__close_redirection()
        except :
            self.logger.error( dump_exc_call_stack() )
            result = False
        self.logger.info( "<<<- %s   End... -------- [r=%s]"%(self.__class__.__name__, 'success' if result else 'failed') )
        # Job里面跑的时候，必须用exit()接口退出
        if bInJob :
            if result : self.succ_exit()            
            self.failed_exit()
        return result
    
if __name__ == "__main__" :
    print("warnning: can not be executed...")
    #case = TnxxxCfg_BaseCase()
    #case.execute()
    #case.execute(False)